﻿using Autodesk.Revit.DB;
using System;
using System.Collections.Generic;
using System.Linq;

namespace CoreDB.Extensions
{
    /// <summary>
    /// 命名规则: 
    /// 1. 在能清晰表达方法意思的前提下尽可能的简单。该类中的方法是针对点的，调用者已经知道调用方的类型是
    ///    三维坐标点，因此方法名中可以省略Point字样。
    /// 2. 向量是自由的，参数名应该将vector与point区分开来。
    /// </summary>
    public static class PointExtension
    {
        private const double defaultEps = 1e-3;

        #region 平行，垂直判断
        /// <summary>
        /// 判断两个点是否相同
        /// </summary>
        /// <param name="point"></param>
        /// <param name="another"></param>
        /// <param name="ignoreZ">True：无视Z值</param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool IsEqual(this XYZ point, XYZ another, double eps = defaultEps, bool ignoreZ = false)
        {
            return point.X.IsEqual(another.X, defaultEps) && point.Y.IsEqual(another.Y, defaultEps) &&
                (point.Z.IsEqual(another.Z, defaultEps) || ignoreZ);
        }
        /// <summary>
        /// 判断两个向量的方向是否相同
        /// 注意：0向量的方向与任何方向相同，所以返回值必然为True
        ///      如果需要判断，自行使用IsZeroLength
        /// </summary>
        /// <param name="vector1"></param>
        /// <param name="vector2"></param>
        /// <param name="ignoreOpp">True:只支持相同方向</param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool IsParallel(this XYZ vector1, XYZ vector2, double eps = defaultEps, bool ignoreOpp = false)
        {
            XYZ dir1 = vector1.Normalizex();
            XYZ dir2 = vector2.Normalizex();
            return (dir1.IsEqual(dir2, eps)) || (dir1.Negate().IsEqual(dir2, eps) && ignoreOpp);
        }
        /// <summary>
        /// 判断指定向量是否与另一个向量垂直
        /// 用点乘判断垂直不是最好的方式，且不需要对向量使用 UnitVector()
        /// </summary>
        /// <param name="vector"></param>
        /// <param name="another"></param>
        /// <param name="eps"></param>
        public static bool IsVerticalTo(this XYZ vector, XYZ another, double eps = defaultEps)
        {
            return vector.DotProduct(another).IsZero(eps);
        }
        #endregion

        #region 投影 Project
        /// <summary>
        /// 点与线的投影，返回多个参数
        /// </summary>
        /// <param name="point">点</param>
        /// <param name="line"></param>
        /// <param name="isVerticalProject">True:把线段变成直线后，垂直投影</param>
        /// <returns>投影点，投影方向，投影距离</returns>
        public static (XYZ projectPt, XYZ projectDir, double projectDistance) ProjectM
            (this XYZ point, Line line, bool isVerticalProject = false,double eps=defaultEps)
        {
            Line cloneLine = line.CloneLine();
            if (cloneLine.IsBound && isVerticalProject)
                cloneLine = cloneLine.GetUnBound();
            XYZ projPt = cloneLine.Project(point)?.XYZPoint;
            XYZ projDir = (projPt - point).Normalizex(eps);
            double projDist = projPt.DistanceTo(point);
            return (projPt, projDir, projDist);
        }
        /// <summary>
        /// 判指点是否能够垂直投影到指定线段上
        /// </summary>
        /// <param name="point"></param>
        /// <param name="line"></param>
        /// <returns></returns>
        public static bool CanVerticalProject(this XYZ point, Line line, double eps = defaultEps)
        {
            var projectModel = point.ProjectM(line);
            return line.Direction.IsVerticalTo(projectModel.projectDir, eps);
        }
        /// <summary>
        /// 点投影到面
        /// </summary>
        /// <param name="point"></param>
        /// <param name="plane"></param>       
        /// <returns>投影点 与 投影距离</returns>
        public static (XYZ projPt, double dist) ProjectM(this XYZ point, Plane plane)
        {
            plane.Project(point, out UV uv, out double dist);
            return (uv == null ? null : plane.Origin + plane.XVec * uv.U + plane.YVec * uv.V, dist);
        }
        /// <summary>
        /// Face 是有边界的
        /// </summary>
        /// <param name="point"></param>
        /// <param name="face"></param>
        /// <returns></returns>
        public static (XYZ projPt, double dist) ProjectM(this XYZ point, Face face)
        {
            IntersectionResult ir = face.Project(point);
            if (ir != null)
            {

            }
            return (ir.XYZPoint, ir.Distance);
        }

        /// <summary>
        /// 点投影到面，可以只给 Plane 的生成条件
        /// </summary>
        /// <param name="point"></param>
        /// <param name="origin"></param>
        /// <param name="normal"></param>
        /// <returns></returns>
        public static (XYZ projPt, double dist) ProjectM(this XYZ point, XYZ origin, XYZ normal)
        {
            var plane = Plane.CreateByNormalAndOrigin(normal, origin);
            return point.ProjectM(plane);
        }
        #endregion

        #region 矩阵变换
        /// <summary>
        /// point 点的矩阵变换
        /// </summary>
        /// <param name="point"></param>
        /// <param name="transform"></param>
        /// <param name="inverse"></param>
        /// <returns></returns>
        public static XYZ OfTransPt(this XYZ point, Transform transform, bool inverse = false)
        {
            if (point is null)
                throw new ArgumentNullException(nameof(point));
            if (transform is null)
                throw new ArgumentNullException(nameof(transform));
            if (inverse)
                transform = transform.Inverse;
            return transform.OfPoint(point);
        }
        /// <summary>
        /// vector 向量的矩阵变换
        /// </summary>
        /// <param name="vector"></param>
        /// <param name="transform"></param>
        /// <param name="inverse"></param>
        /// <returns></returns>
        public static XYZ OfTransVector(this XYZ vector, Transform transform, bool inverse = false)
        {
            if (vector is null)
                throw new ArgumentNullException(nameof(vector));
            if (transform is null)
                throw new ArgumentNullException(nameof(transform));
            if (inverse)
                transform = transform.Inverse;
            return transform.OfVector(vector);
        }
        #endregion

        #region 基本方法

        /// <summary>
        /// 将矢量规范化为向量，替代Normalize,用后缀x区分
        /// </summary>
        /// <param name="vector"></param>
        /// <returns></returns>
        public static XYZ Normalizex(this XYZ vector,double eps=defaultEps)
        {
            double length = vector.GetLength();
            if (length.IsZero(eps))//如果长短过短，返回0向量
                return new XYZ();
            return new XYZ(vector.X / length, vector.Y / length, vector.Z / length);
        }

        public static XYZ Offset(this XYZ point, XYZ direction, double distance)
        {
            return point + direction.Normalizex() * distance;
        }

        /// <summary>
        /// 将指定的点拍平，即格式化Z坐标值
        /// </summary>
        /// <param name="point">指定点</param>
        /// <param name="z">扁平化后的Z坐标值</param>
        /// <returns></returns>
        public static XYZ Flat(this XYZ point, double z = 0)
        {
            return new XYZ(point.X, point.Y, z);
        }
        /// <summary>
        /// 重载，有时候拍平不想输入z值，直接输入点即可
        /// </summary>
        /// <param name="point"></param>
        /// <param name="zPt"></param>
        /// <returns></returns>
        public static XYZ Flat(this XYZ point, XYZ zPt)
        {
            return point.Flat(zPt.Z);
        }

        /// <summary>
        /// Need Improved:求出来的不一定是角度
        /// 得到在忽略Z值情况下从原始向量(调用者)到目标向量(参数)之间的角度，其中正值为逆时针，负值为顺时针。
        /// </summary>
        /// <param name="targetVector">目标向量</param>
        /// <param name="originVector">原始向量</param>
        /// <returns>原始向量到目标向量之间的角度，其中正值为逆时针，负值为顺时针</returns>
        public static double AngleToWithDirection(this XYZ originVector, XYZ targetVector)
        {
            var adjustedVec = targetVector.Flat(originVector.Z);
            var angle = originVector.AngleTo(adjustedVec);
            return originVector.CrossProduct(adjustedVec).IsParallel(XYZ.BasisZ, defaultEps, true) ? angle : -angle;
        }
        public static XYZ Clone(this XYZ point)
        {
            return new XYZ(point.X, point.Y, point.Z);
        }
        /// <summary>
        /// 用于验证两个点能否连成一条直线
        /// </summary>
        /// <param name="start"></param>
        /// <param name="end"></param>
        /// <returns></returns>
        public static bool CanMakeBound(this XYZ start, XYZ end)
        {
            try
            {
                Line ln = Line.CreateBound(start, end);
                return (null != ln);
            }
            catch (System.Exception)
            {
                return false;
            }
        }
        #endregion

        /// <summary>
        /// 判断点是否在 Curve 上
        /// </summary>
        /// <param name="baseCurve">The case curve to check.</param>
        /// <param name="point">The point to check.</param>
        /// <param name="excludeEnds">True:是否包含起点与终点</param>
        /// <param name="eps">The compare tolearance.</param>
        /// <returns></returns>
        public static bool IsOnCurve(this XYZ point, Curve curve, bool includeEnds = true, double eps = defaultEps)
        {
            if (null == curve || null == point)
                return false;
            try
            {
                if (curve.Distance(point).IsZero(eps))
                    if (includeEnds && (point.IsEqual(curve.Start(), eps) || point.IsEqual(curve.End(), eps)))
                        return false;
            }
            catch (Autodesk.Revit.Exceptions.InvalidOperationException)
            {
                return false;
            }
            return true;
        }
        public static bool IsOnFace(this XYZ point, Face face, double eps = defaultEps)
        {
            IntersectionResult ir = face.Project(point);
            return ir == null ? false : ir.Distance.IsZero(eps);
        }
        /// <summary>
        /// Surface Plane 都是抽象的，无限的
        /// </summary>
        /// <param name="point"></param>
        /// <param name="surface"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static bool IsOnSurface(this XYZ point, Surface surface, double eps = defaultEps)
        {
            surface.Project(point, out UV uv, out double distance);
            return distance.IsZero(eps);
        }
        public static bool IsOnPlane(this XYZ point, Plane plane, double eps = defaultEps)
        {
            return point.ProjectM(plane).dist.IsZero(eps);
        }



    }

    /// <summary>
    /// 在合理的情况下尽可能地对IEnumerable<XYZ>进行扩展， 而不是IColleciton<XYZ>和IList<XYZ>。
    /// 为了使用方便，如果集合较为轻量，应尽可能地返回List。
    /// </summary>
    public static class PointsExtension
    {
        private const double defaultEps = 1e-3;
        /// <summary>
        /// 去除点集合中重复的点
        /// </summary>
        /// <param name="points">指定的点集合</param>
        /// <param name="ignoreZ">True:忽略坐标z值</param>
        /// <returns>非重复的点集合</returns>
        public static List<XYZ> DistinctPoints(this IEnumerable<XYZ> points, bool ignoreZ = false, double eps = defaultEps)
        {
            return points.Distinct(new PointComparer(ignoreZ, eps)).ToList();
        }

        #region 基本方法
        /// <summary>
        /// 批量格式化Z值
        /// </summary>
        /// <param name="pts"></param>
        /// <param name="z"></param>
        /// <returns></returns>
        public static List<XYZ> Flat(this IEnumerable<XYZ> pts, double z = 0)
        {
            return pts.Select(x => x.Flat(z)).ToList();
        }
        /// <summary>
        /// 求点集的中心/平均点/质心
        /// 对点集来说，这三者是一个意思
        /// </summary>
        /// <param name="points"></param>
        /// <param name="distinctPoint">True:去除重复点</param>
        /// <returns></returns>
        public static XYZ AveragePoint(this IEnumerable<XYZ> points, bool distinctPoint = false)
        {
            XYZ PointSum = new XYZ();
            var pts = distinctPoint ? points.DistinctPoints() : points.ToList();
            pts.ForEach(pt => PointSum += pt);

            if (PointSum.IsEqual(XYZ.Zero) || pts.Count == 0)
                return XYZ.Zero;
            return PointSum / pts.Count();
        }

        public static List<XYZ> Clone(this IEnumerable<XYZ> points)
        {
            return points.Select(x => x.Clone()).ToList();
        }
        #endregion


        #region Matrix 矩阵点线方法
        /// <summary>
        /// 传入若干个点，可以计算出以这些点的坐标为基础的所有点
        /// </summary>
        /// <param name="points"></param>
        /// <returns></returns>
        public static List<XYZ> FindMatrixPts(this IEnumerable<XYZ> points, double eps = defaultEps)
        {
            var pts = points.ToList();
            if (pts.Count == 0)
                return new List<XYZ>();

            return FindMatrixPts(points.Select(x => x.X), points.Select(x => x.Y), points.Select(x => x.Z), eps);
        }
        /// <summary>
        /// 传入若干个点，可以计算出以这些点的坐标为基础的所有点
        /// 如果传入3个x，2个y，5个z，将产生 3*2*5 = 30 个点
        /// </summary>
        /// <param name="xs"></param>
        /// <param name="ys"></param>
        /// <param name="zs"></param>
        /// <returns></returns>
        public static List<XYZ> FindMatrixPts(this IEnumerable<double> xs, IEnumerable<double> ys, IEnumerable<double> zs, double eps = defaultEps)
        {
            var Xs = xs.Distinct(new DoubleComparer(eps)).ToList();
            var Ys = ys.Distinct(new DoubleComparer(eps)).ToList();
            var Zs = zs.Distinct(new DoubleComparer(eps)).ToList();

            List<XYZ> resultPts = new List<XYZ>();
            foreach (var x in Xs)
                foreach (var y in Ys)
                    foreach (var z in Zs)
                        resultPts.Add(new XYZ(x, y, z));
            return resultPts;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="points"></param>
        /// <param name="axisIndex">轴号编号，x,y,z，分别是0,1,2</param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static List<double> FindMatrixDoubles(this IEnumerable<XYZ> points, int axisIndex = 0, double eps = defaultEps)
        {
            if (axisIndex == 0)
                return points.Select(x => x.X).Distinct(new DoubleComparer(eps)).ToList();
            else if (axisIndex == 1)
                return points.Select(x => x.Y).Distinct(new DoubleComparer(eps)).ToList();
            else if (axisIndex == 2)
                return points.Select(x => x.Z).Distinct(new DoubleComparer(eps)).ToList();
            else
                return new List<double>();
        }
        /// <summary>
        /// 获得矩阵的线段（不保证首尾相连）
        /// </summary>
        /// <param name="xs"></param>
        /// <param name="ys"></param>
        /// <param name="zs"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static List<Line> FindMatrixLines(this IEnumerable<double> xs, IEnumerable<double> ys, IEnumerable<double> zs, double eps = defaultEps)
        {
            var Xs = xs.Distinct(new DoubleComparer(eps)).OrderBy(x => x).ToList();
            var Ys = ys.Distinct(new DoubleComparer(eps)).OrderBy(x => x).ToList();
            var Zs = zs.Distinct(new DoubleComparer(eps)).OrderBy(x => x).ToList();

            List<Line> resultLines = new List<Line>();
            if ((Xs.Count > 1 || Ys.Count > 1 || Zs.Count > 1) && (Xs.Count > 0 && Ys.Count > 0 && Zs.Count > 0))
            {
                for (int i = 0; i < Xs.Count; i++)
                {
                    for (int j = 0; j < Ys.Count; j++)
                    {
                        for (int k = 0; k < Zs.Count; k++)
                        {
                            XYZ st = new XYZ(Xs[i], Ys[j], Zs[k]);
                            if (i + 1 < Xs.Count)
                            {
                                XYZ ed = new XYZ(Xs[i + 1], Ys[j], Zs[k]);
                                if (st.CanMakeBound(ed))
                                    resultLines.Add(Line.CreateBound(st, ed));
                            }
                            if (j + 1 < Ys.Count)
                            {
                                XYZ ed = new XYZ(Xs[i], Ys[j + 1], Zs[k]);
                                if (st.CanMakeBound(ed))
                                    resultLines.Add(Line.CreateBound(st, ed));
                            }
                            if (k + 1 < Zs.Count)
                            {
                                XYZ ed = new XYZ(Xs[i], Ys[j], Zs[k + 1]);
                                if (st.CanMakeBound(ed))
                                    resultLines.Add(Line.CreateBound(st, ed));
                            }
                        }
                    }
                }
            }
            return resultLines;
        }
        /// <summary>
        /// 获得矩阵的线段（不保证首尾相连）
        /// </summary>
        /// <param name="points"></param>
        /// <param name="eps"></param>
        /// <returns></returns>
        public static List<Line> FindMatrixLines(this IEnumerable<XYZ> points, double eps = defaultEps)
        {
            return FindMatrixLines(points.Select(x => x.X), points.Select(x => x.Y), points.Select(x => x.Z), eps);
        }
        #endregion


    }



    public class PointComparer : IEqualityComparer<XYZ>
    {
        public bool IgnoreZ { get; set; }

        public double Eps { get; set; }
        public PointComparer(bool ignoreZ = false, double eps = 0.001)
        {
            this.IgnoreZ = ignoreZ;
            this.Eps = eps;
        }
        public bool Equals(XYZ x, XYZ y)
        {
            return x.IsEqual(y, Eps, IgnoreZ);
        }
        /// <summary>
        /// 始终指明相等，让处理相等逻辑继续交给Equals。
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public int GetHashCode(XYZ obj)
        {
            return obj.ToString().GetHashCode();
        }
    }
}
